<template>
    <div class="three-demo-page">
        <div style="z-index: 1001">
            <n-space>
                <n-button
                    type="primary"
                    @click="toggleViewer"
                    style="z-index: 1001"
                >
                    切换{{ viewer ? "俯视" : "第一人称" }}视角
                </n-button>
                <n-button type="primary" @click="onMove" style="z-index: 1001">
                    移动
                </n-button>
            </n-space>
        </div>
        <LoadModal @load="onLoad" />
    </div>
</template>
<script setup>
import { onBeforeUnmount, onMounted, ref } from "vue";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
import * as THREE from "three";
import AsyncComp from "@/utils/AsyncComp";
import TWEEN from "@tweenjs/tween.js";
import { Octree } from "three/addons/math/Octree.js";

import { Capsule } from "three/addons/math/Capsule.js";

const LoadModal = AsyncComp(() => import("./LoadModal.vue"));

let Scene = null;
let Camera = null;
let Controls = null;
let Renderer = null;
let ScreenDom = null;
let Controls1 = null;
let Controls2 = null;

const onLoad = (scene, camera, screenDom, renderer, controls1, controls2) => {
    Scene = scene;
    Camera = camera;
    ScreenDom = screenDom;
    Renderer = renderer;
    Controls1 = controls1;
    Controls2 = controls2;
    const _render = () => {
        requestAnimationFrame(_render);
        TWEEN.update();
    };
    _render();
    loadGltf();
};

const worldOctree = new Octree();
const clock = new THREE.Clock();

const loadGltf = () => {
    const loader = new GLTFLoader();
    const dracoLoader = new DRACOLoader();
    dracoLoader.setDecoderPath("/static/wasms/");
    dracoLoader.setDecoderConfig({ type: "js" });
    // loader.setDRACOLoader(dracoLoader);
    loader.load("/static/model/collision-world.glb", (gltf) => {
        const model = gltf.scene;
        model.name = "智慧教室";

        Scene.add(model); // 加入场景

        gltf.scene.traverse((child) => {
            if (child.isMesh) {
                child.castShadow = true;
                child.receiveShadow = true;
                if (child.material.map) {
                    child.material.map.anisotropy = 4;
                }
            }
        });
        Scene.add(model);

        worldOctree.fromGraphNode(model);
        onPlay();
    });
};

const viewer = ref(true);
const toggleViewer = () => {
    if (viewer.value) {
        //切换俯视视角
        new TWEEN.Tween(Camera.position)
            .to({ x: 30, y: 30, z: 30 }, 1000)
            .easing(TWEEN.Easing.Quadratic.InOut)
            .onUpdate((v) => {
                Camera.position.set(v.x, v.y, v.z);
            })
            .start();
        Controls1.enabled = true;
    } else {
        //切换第一人称视角
        Controls1.enabled = false;
        new TWEEN.Tween({
            ...Camera.position,
            _x: Camera.rotation.x,
            _y: Camera.rotation.y,
        })
            .to({ x: 0, y: 1.2, z: 0, _x: 0, _y: 0 }, 1000)
            .easing(TWEEN.Easing.Quadratic.InOut)
            .onUpdate((v) => {
                Camera.position.set(v.x, v.y, v.z);
                Camera.rotation.x = v._x;
                Camera.rotation.y = v._y;
            })
            .onComplete(() => {
                onPlay();
            })
            .start();
    }
    viewer.value = !viewer.value;
};

let playerCollider = null;

//移动视角胶囊位置
const onMove = () => {
    let _position1 = new THREE.Vector3(1, 1.35, 1);
    let _position2 = new THREE.Vector3(1, 1, 1);
    playerCollider.end = _position1;
    playerCollider.start = _position2;
};

const onPlay = () => {
    const GRAVITY = 30;
    const STEPS_PER_FRAME = 5;

    playerCollider = new Capsule(
        new THREE.Vector3(0, 0.35, 0),
        new THREE.Vector3(0, 1.2, 0),
        0.35
    );

    const playerVelocity = new THREE.Vector3();
    const playerDirection = new THREE.Vector3();

    let playerOnFloor = false;

    const keyStates = {};

    document.addEventListener("keydown", (event) => {
        keyStates[event.code] = true;
    });

    document.addEventListener("keyup", (event) => {
        keyStates[event.code] = false;
    });

    let isDown = false;
    ScreenDom.addEventListener("mousedown", () => {
        isDown = true;
    });

    document.addEventListener("mouseup", () => {
        isDown = false;
    });

    document.body.addEventListener("mousemove", (event) => {
        if (isDown) {
            let _x = Camera.rotation.x - event.movementY / 600;
            Camera.rotation.y -= event.movementX / 600;
            //限制视角
            if (_x <= 0) {
                Camera.rotation.x = Math.max(_x, -0.95);
            } else {
                Camera.rotation.x = Math.min(_x, 0.5);
            }
        }
    });

    function playerCollisions() {
        const result = worldOctree.capsuleIntersect(playerCollider);

        playerOnFloor = false;
        if (result) {
            playerOnFloor = result.normal.y > 0;
            if (!playerOnFloor) {
                playerVelocity.addScaledVector(
                    result.normal,
                    -result.normal.dot(playerVelocity)
                );
            }

            playerCollider.translate(
                result.normal.multiplyScalar(result.depth)
            );
        }
    }

    function updatePlayer(deltaTime) {
        let damping = Math.exp(-4 * deltaTime) - 1;
        if (!playerOnFloor) {
            playerVelocity.y -= GRAVITY * deltaTime;
            damping *= 0.1;
        }
        playerVelocity.addScaledVector(playerVelocity, damping);
        const deltaPosition = playerVelocity.clone().multiplyScalar(deltaTime);
        playerCollider.translate(deltaPosition);
        playerCollisions();
        Camera.position.copy(playerCollider.end);
    }

    function getForwardVector() {
        Camera.getWorldDirection(playerDirection);
        playerDirection.y = 0;
        playerDirection.normalize();
        return playerDirection;
    }

    function getSideVector() {
        Camera.getWorldDirection(playerDirection);
        playerDirection.y = 0;
        playerDirection.normalize();
        playerDirection.cross(Camera.up);

        return playerDirection;
    }

    function controls(deltaTime) {
        const speedDelta = deltaTime * (playerOnFloor ? 25 : 8);
        if (keyStates["KeyW"]) {
            playerVelocity.add(getForwardVector().multiplyScalar(speedDelta));
        }

        if (keyStates["KeyS"]) {
            playerVelocity.add(getForwardVector().multiplyScalar(-speedDelta));
        }

        if (keyStates["KeyA"]) {
            playerVelocity.add(getSideVector().multiplyScalar(-speedDelta));
        }

        if (keyStates["KeyD"]) {
            playerVelocity.add(getSideVector().multiplyScalar(speedDelta));
        }

        if (playerOnFloor) {
            if (keyStates["Space"]) {
                playerVelocity.y = 6;
            }
        }
    }

    function animate() {
        if (Controls1.enabled) return;
        const deltaTime = Math.min(0.05, clock.getDelta()) / STEPS_PER_FRAME;
        for (let i = 0; i < STEPS_PER_FRAME; i++) {
            controls(deltaTime);
            updatePlayer(deltaTime);
        }
        requestAnimationFrame(animate);
    }
    animate();
};

onBeforeUnmount(() => {
    let _label = Scene.getObjectByName("智慧教室");
    Scene.remove(_label);
});
</script>
